package aml.parse.node;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Stack;

import aml.parse.ParseNode;
import aml.runtime.Context;
import aml.runtime.Value;
import aml.runtime.ValueRef;

public class Expr extends ParseNode {
	private ParseNode root;
	
	public Expr(List<ParseNode> nodes) {
		// no Op instances in nodes but OpNode instances
		List<ParseNode> infixNodes = makeInfix(nodes);
		List<ParseNode> suffixNodes = makeSuffix(infixNodes);
		root = makeParseTree(suffixNodes);
	}
	
	private List<ParseNode> makeInfix(List<ParseNode> nodes) {	// TODO add support to unary operators
		List<ParseNode> infix = new ArrayList<ParseNode>();
		Iterator<ParseNode> it = nodes.iterator();
		int contEntCnt = 0;	// continuous entity count
		while (it.hasNext()) {
			ParseNode n = it.next();
			if (n instanceof OpNode) {
				contEntCnt = 0;	// clear count
			}
			else {
				contEntCnt += 1;	// increase count
			}
			if (contEntCnt >= 2) {
				infix.add(new OpAutoSend());
			}
			infix.add(n);
		}
		return infix;
	}
	
	private List<ParseNode> makeSuffix(List<ParseNode> infixNodes) {
		List<ParseNode> suffix = new ArrayList<ParseNode>();
		Stack<ParseNode> stack = new Stack<ParseNode>();
		stack.push(new OpEOL() {
			@Override
			public int precedence() {
				return -2000000000;
			}
		});
		Iterator<ParseNode> it = infixNodes.iterator();
		while (it.hasNext()) {
			ParseNode n = it.next();
			while (stack.peek().precedence() > n.precedence()) {
				suffix.add(stack.pop());
			}
			while (stack.peek().precedence() == n.precedence()) {
				if (n.isRightAssoc()) {
					stack.push(n);
					break;
				}
				suffix.add(stack.pop());
			}
			if (stack.peek().precedence() < n.precedence()) {
				stack.push(n);
			}
		}
		while (stack.size() > 1) {
			suffix.add(stack.pop());
		}
		return suffix;
	}

	private ParseNode makeParseTree(List<ParseNode> suffixNodes) {
		Stack<ParseNode> stack = new Stack<ParseNode>();
		Iterator<ParseNode> it = suffixNodes.iterator();
		while (it.hasNext()) {
			ParseNode n = it.next();
			if (n instanceof OpNode) {
				OpNode op = (OpNode)n;
				if (op.isUnary() || stack.size() == 1) {
					ParseNode rhs = stack.pop();
					stack.push(op.setRight(rhs));
				}
				else {	// binary
					ParseNode rhs = stack.pop();
					ParseNode lhs = stack.pop();
					stack.push(op.setLeft(lhs).setRight(rhs));
				}
			}
			else {
				stack.push(n);
			}
		}
		return stack.pop();
	}

	@Override
	public Value eval(Context ctx) {
		return root.eval(ctx);
	}
	
	@Override
	public ValueRef eref(Context ctx) {
		return root.eref(ctx);
	}

	@Override
	public String toString() {
		return root.toString();
	}
}